import numpy as np
import math

DNA_bite = 13  # 一个DNA的二进制位数,(第一维表示符号位)
Int_bite = 2  # DNA_bite-1（符号位bit）之后整数占的bit位
DNA_number = 2  # DNA的个数
animal_number = 200  # 开始种群的数量
cross_rating = 0.8  # 生殖交叉概率
variation_rating = 0.005  # 变异的概率
generator_time = 50  # 种群演变的次数
limit_area = [-3, 3]  # 值域

# 目标函数
def f(x, y):
    return 3 * (1 - x) ** 2 * np.exp(-(x ** 2) - (y + 1) ** 2) - 10 * (x / 5 - x ** 3 - y ** 5) * np.exp(
        -x ** 2 - y ** 2) - 1 / 3 ** np.exp(-(x + 1) ** 2 - y ** 2)  # 可自定义修改


def translate_DNA(animal):  # 解码种群的DNA
    def DNA2t10(DNA):
        sum = 0
        sign = DNA[0]
        data = DNA[1:]
        if sign == 0:  # 符号位赋值
            flag = -1
        else:
            flag = 1

        for i in range(0, Int_bite):
            if data[i] == 1:
                sum += math.pow(2, Int_bite - i - 1)
        for i in range(Int_bite, len(data)):
            if data[i] == 1:
                sum += math.pow(2, Int_bite - i - 1)
        return flag * sum

    DNA_ans = []
    for i in range(0, DNA_bite * DNA_number, DNA_bite):
        DNA = animal[i:i + DNA_bite]
        translated_DNA = DNA2t10(DNA)
        DNA_ans.append(translated_DNA)
    return DNA_ans


def flag_limit_area(animal, limit_area):  # 判断种群是否符合值域，否则一票否决
    x, y = translate_DNA(animal)
    if x <= limit_area[1] and x >= limit_area[0] and y <= limit_area[1] and y >= limit_area[0]:
        return True
    else:
        return False

def get_fitnessne(animals_monster):  # 计算种群各个部分的适应度
    fitness_score = np.zeros(len(animals_monster))
    fit_flag = np.zeros(len(animals_monster))
    for i in range(len(animals_monster)):
        x, y = translate_DNA(animals_monster[i])
        fitness_score[i] = f(x, y)
        if flag_limit_area(animals_monster[i], limit_area):
            fit_flag[i] = 1
        else:
            fit_flag[i] = 0
    fitness_score = (fitness_score - np.min(fitness_score)) + 1e-5
    fitness_score = fitness_score * fit_flag  # 如果不符合定义域就不取了
    fitness_p = fitness_score / (fitness_score.sum())  # 计算被选择的概率
    return fitness_p


def select_animal(animals_monster, fitness):  # 按照适应度选择留下的种群
    idx = np.random.choice(np.arange(animal_number), size=animal_number, replace=True, p=(fitness) / (fitness.sum() + 1e-8))
    return animals_monster[idx]


def variation(children, variation_rating):  # 模拟编译
    if np.random.rand() < variation_rating:  # 以MUTATION_RATE的概率进行变异
        mutate_point = np.random.randint(0, DNA_bite * 2)  # 随机产生一个实数，代表要变异基因的位置
        children[mutate_point] = children[mutate_point] ^ 1  # 这一位取反
    return children


def crossover_and_variation(animals_monster, cross_rating):  # 模拟生殖过程（包括交配和变异）
    new_animals = []
    for father in animals_monster:
        child = father  # 选择父亲
        if np.random.rand() < cross_rating:  # 产生子代时不是必然发生交叉，而是以一定的概率发生交叉
            mother = animals_monster[np.random.randint(animal_number)]  # 再选择母亲
            cross_points = np.random.randint(low=0, high=DNA_bite * DNA_number)  # 随机产生交叉的点
            child[cross_points:] = mother[cross_points:]  # 交叉互换，模拟生殖
        variation(child, variation_rating)  # 变异
        new_animals.append(child)
    return np.array(new_animals)


def get_ans(animals_monster):  # 获取结果
    fitness = get_fitnessne(animals_monster)
    max_fitness_index = np.argmax(fitness)
    print("max_fitness:", fitness[max_fitness_index])
    x, y = translate_DNA(animals_monster[max_fitness_index])
    print("最优的基因型：", animals_monster[max_fitness_index])
    print("(x, y):", (x, y), f(x, y))
    return


# 初始化种群，需要判断开始的种群是否符合值域
animals_monster = np.random.randint(2, size=(animal_number, DNA_bite * DNA_number))
num_sum = animal_number
while (num_sum):
    pos = num_sum - 1
    if flag_limit_area(animals_monster[pos], limit_area):
        num_sum -= 1
    else:
        animals_monster[pos] = np.random.randint(2, size=(1, DNA_bite * DNA_number))

# 模拟进化选择generator_n轮
for i in range(generator_time):
    fitness_score = get_fitnessne(animals_monster)
    selected_animals = select_animal(animals_monster, fitness_score)
    animals_monster = crossover_and_variation(selected_animals, cross_rating)

get_ans(animals_monster)
